/*
 * sched.c 是核心的内核文件，包含了最原始的调度（休眠/唤醒/调度等），
 * 		以及一些简单的系统调用（例如getpid：从cuurent任务中获取一个字段)	
 */
#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/sys.h>
#include <linux/mm.h>
#include <asm/system.h>
#include <asm/io.h>

#define LATCH (1193180/HZ)

extern int timer_interrupt(void);
extern int system_call(void);

/**
 * 这个联合体有点意思，stack 是一个长度为 4096 字节的数组，刚好是一个页的长度
 * struct task_struct 是任务的定义，包含任务运行时需要的信息
 * 也就是说，task 是在这个 4096 字节的低字节开始位置存放
 */
union task_union {
	struct task_struct task;
	char stack[PAGE_SIZE];
};
static union task_union init_task = {INIT_TASK,}; /* init_task 结构要4K对齐吗 */

unsigned long volatile jiffies=0;

struct task_struct *current = &(init_task.task);
struct task_struct *last_task_used_math = NULL;

struct task_struct * task[NR_TASKS] = {&(init_task.task), };

void math_state_restore()
{

}

void schedule(void)
{

}

void do_timer(long cpl)
{

}

void sched_init(void)
{
    int i;
	struct desc_struct * p; /* 描述符结构体，实际就是指向一个256 * 8的数组 */

	/* 要求信号结构体的长度只能是16字节，当前这个结构体只有4项 */
    if (sizeof(struct sigaction) != 16) {
        panic("Struct sigaction MUST be 16 bytes");
    }

    /* 第一个tss段描述符，在gdt中的设置 */
	set_tss_desc(gdt + FIRST_TSS_ENTRY, &(init_task.task.tss));
	/* 第一个ldt段描述符，在gdt中的设置 */
	set_ldt_desc(gdt + FIRST_LDT_ENTRY, &(init_task.task.ldt));
	/* 上述2个段描述符，共同构成了任务0的段描述信息 */

	/* 跳过任务0的2个描述符 */
	p = gdt + 2 + FIRST_TSS_ENTRY;
	/**
	 * 当前最多只支持64个任务，从1开始初始化
	 * 		（0已经在上面几行初始化过了task[0] = &(init_task.task) ）
	 * 其他的任务当前还未创建，因此任务都初始化为空，描述符的内容都初始化为0
	 * 每个任务有两个描述符，分别是对应任务的 ldt 和 tss
	 * */
	for(i = 1; i < NR_TASKS; i++) {
		task[i] = NULL;
		p->a = p->b = 0;
		p++;
		p->a = p->b = 0;
		p++;
	}

	/**
	 * 清除任务嵌套标志，清除NT位可确保中断返回（如IRET指令）时不会触发任务切换
	 * 作用：
	 * 	某些场景（如操作系统内核）需确保中断处理返回时不发生任务切换，清除NT位可避免此风险。
	 * 	当不确定NT位状态时（如切换执行环境），强制将其设为0以保证后续操作确定性。
	 * */
	__asm__("pushfl ; andl $0xffffbfff,(%esp) ; popfl");

	/* 加载任务0的 tss 和 ldt， 为任务切换做准备 */
	ltr(0);
	lldt(0);

	/* 设置定时中断的频率： */
	/* 控制字：设置通道0工作在方式3，计数初值采用二进制 */
	outb_p(0x36,0x43);
	/**
	 * LATCH = 1193180/100，频率为100HZ，也就是 10ms 一次中断
	 * 先写低字节8bit，然后写高字节8bit
	 * */
	outb_p(LATCH & 0xff , 0x40); 	/* LSB */
	outb(LATCH >> 8 , 0x40);		/* MSB */
	
	/**
	 * 注册定时中断处理函数
	 * 中断号：0x20；中断处理函数：_timer_interrupt，位于 kernel/sys_call.s
	 * */
	set_intr_gate(0x20,&timer_interrupt);

	/* 
	 * 8259A中断处理器控制硬件操作
	 * OCW1操作
	 * 	打开主8259A的IRQ0中断，也就是定时器中断
	 */
	outb(inb_p(0x21)&~0x01,0x21);

	/**
	 * 注册系统调用中断
	 * 中断号：0x80；中断处理函数：_system_call，位于 kernel/sys_call.s
	 * */
	set_system_gate(0x80,&system_call);
}